local M = {}

local function get_split(s, rep, pos)
    if pos >= #s then
        return nil
    end
    local find = string.find(s, rep, pos)
    if not find then
        return nil
    end
    local data = string.sub(s, pos, find - 1)
    return data, find
end

local function read_header_line(channel, max_len)
    local header = {}
    while true do
        local line = channel.read_line("\r\n", max_len)
        assert(line, "closed")
        if line == "" then break end
        local k, v = string.match(line, "([^:^ ]+)[: ]+([^ ]+)")
        assert(k and v, "bad header line")
        k = string.lower(k)
        if header[k] then
            if type(header[k]) == "string" then
                header[k] = { header[k]}
            end
            table.insert(header[k], v)
        else
            header[k] = v
        end
    end
    return header
end

local function read_chunk(channel)
    local len = channel.read_line("\r\n")
    assert(len and len  ~= "")
    len = tonumber("0x" .. len)
    local t = {}

    while len > 0 do
        local data = channel.read(len)
        channel.read(2)
        len = channel.read_line("\r\n")
        assert(len and len  ~= "")
        len = tonumber("0x" .. len)

        table.insert(t, data)
    end
    channel.read(2)
    return table.concat(t, "")
end

local function read_body(header, channel)
    local clen = header["content-length"]
    local chunked = header["transfer-encoding"] or ""
    chunked = chunked == "chunked"
    if not clen and not chunked then
        return ""
        --error("not conten-length or  chunked")
    end
    local body
    if chunked then
        body = read_chunk(channel)
    else
        clen = tonumber(clen)
        if clen > 0 then
            body = channel.read(clen)
        else
            body = ""
        end
    end
    return body
end
M.read_body = read_body

function M.read_request_header(channel, max_len)
    local first = channel.read_line("\r\n", max_len)
    assert(first, "closed")
    local t = {}
    t.first = first
    local pos = 1
    t.method, pos = get_split(first, " ", pos)
    assert(t.method, "closed")
    t.uri, pos = get_split(first, " ", pos + 1)
    assert(t.uri, "closed")
    t.version = string.sub(first, pos + 1)
    t.header = read_header_line(channel)
    return t
end

function M.read_response_header(channel)
    local first = channel.read_line("\r\n")
    assert(first, "closed")
    local t = {}
    t.first = first
    local pos = 1
    t.version, pos = get_split(first, " ", pos)
    t.code, pos = get_split(first, " ", pos + 1)
    t.code = tonumber(t.code)
    t.code_msg = string.sub(first, pos + 1)
    t.header = read_header_line(channel)
    return t
end

function M.read_request(channel, max_header_len)
    local t = M.read_request_header(channel, max_header_len)
    t.body = read_body(t.header, channel)
    return t
end

function M.read_response(channel, max_header_len)
    local t = M.read_response_header(channel, max_header_len)
    t.body = read_body(t.header, channel)
    return t
end

function M.make_response_header_str(response)
    local t = {
        string.format("%s %d %s", response.version,
            tonumber(response.code), response.code_msg or ""),
    }
    for k, v in pairs(response.header) do
        if type(v) == "table" then
            for _, v_ in ipairs(v) do
                table.insert(t, string.format("%s: %s", k, tostring(v_)))
            end
        else
            table.insert(t, string.format("%s: %s", k, tostring(v)))
        end
    end
    table.insert(t, "\r\n")
    return table.concat(t, "\r\n")
end

function M.escape(s)
    return (string.gsub(s, "([^A-Za-z0-9_])", function(c)
        return string.format("%%%02X", string.byte(c))
    end))
end

local function decode_func(c)
    return string.char(tonumber(c, 16))
end

local function decode(str)
    local str = str:gsub('+', ' ')
    return str:gsub("%%(..)", decode_func)
end

function M.parse(u)
    local path,query = u:match "([^?]*)%??(.*)"
    if path then
        path = decode(path)
    end
    return path, query
end

function M.parse_query(q)
    local r = {}
    for k,v in q:gmatch "(.-)=([^&]*)&?" do
        r[decode(k)] = decode(v)
    end
    return r
end

return M
